package org.ainlolcat.ainscience2.controller.loader.impl;

import org.ainlolcat.ainscience2.controller.loader.ConfigurationLoader;
import org.ainlolcat.ainscience2.controller.loader.LoaderDefinition;
import org.ainlolcat.ainscience2.model.Configuration;
import org.ainlolcat.ainscience2.model.filter.impl.LoaderDefinitionFilter;
import org.ainlolcat.ainscience2.model.impl.ConfigurationImpl;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import java.io.FileReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Created by IntelliJ IDEA.
 * User: ain
 * Date: 17.06.12
 * Time: 1:56
 * To change this template use File | Settings | File Templates.
 */
public class ConfigurationLoaderImpl implements ConfigurationLoader {
    static Logger log = Logger.getLogger(ConfigurationLoaderImpl.class.getName());

    Configuration configuration = new ConfigurationImpl();
    Collection<Element> unresolved = new ArrayList<Element>();
    
    public Configuration loadConfiguration(Document doc, boolean recursively) {
        if (recursively){
            if (doc.getRootElement().getChild("imports")!=null){
                Collection<Element> imports = doc.getRootElement().getChild("imports").getChildren();
                for (Element imp: imports){
                    handleFile(imp.getAttributeValue("filename"));
                }
            }
        }
        if (doc.getRootElement().getChild("definitions")!=null){
            Collection<Element> definitions = doc.getRootElement().getChild("definitions").getChildren();
            for (Element def: definitions){
                handleDefinition(def);
            }
        }
        resolveDependencies();
        return configuration;
    }

    public Configuration loadConfiguration(String filename, boolean recursively) {
        try {
            return loadConfiguration(new SAXBuilder().build(new FileReader(filename)),recursively);
        } catch (Exception e) {
            log.log(Level.WARNING, "Cannot handle file: "+filename, e);
            throw new RuntimeException(e);
        }
    }

    private void handleFile(String filename){
        try {
            Document doc = new SAXBuilder().build(new FileReader(filename));
            loadConfiguration(doc, true);
        } catch (Exception e) {
            log.log(Level.WARNING, "Cannot handle file: "+filename +". Continue processing", e);
        }
    }

    /**
     * return false if cannot resolve parent
     * @param e
     * @return
     */
    private boolean handleDefinition(Element e){
        String parentName = e.getAttributeValue("parent");
        LoaderDefinition parent = null;
        if (parentName != null){
            Collection<LoaderDefinition> parents = configuration.getDefinitions(new LoaderDefinitionFilter(parentName));
            parent = parents.isEmpty()?null:parents.iterator().next();
        }
        if (parentName!= null && parent == null) {
            if (!unresolved.contains(e)){
                unresolved.add(e);
            }
            return false;
        }
        if (parent!=null){
            configuration.addDefinition(parent.createChild(new LoaderDefinition(
                    e.getAttributeValue("instantiable")==null?false:Boolean.parseBoolean(e.getAttributeValue("instantiable")),
                    e.getAttributeValue("name"),
                    e.getAttributeValue("namespace"),
                    e.getAttributeValue("class"),
                    e.getChild("parameters")
            )));
        }else{
            configuration.addDefinition(new LoaderDefinition(
                    e.getAttributeValue("instantiable")==null?false:Boolean.parseBoolean(e.getAttributeValue("instantiable")),
                    e.getAttributeValue("name"),
                    e.getAttributeValue("namespace"),
                    e.getAttributeValue("class"),
                    e.getChild("parameters")
            ));
        }
        return true;
    }

    private void resolveDependencies(){
        while (true){
            Iterator<Element> elementIterator = unresolved.iterator();
            int prevSize = unresolved.size();
            while (elementIterator.hasNext()){
                if (handleDefinition(elementIterator.next())){
                    elementIterator.remove();
                }
            }
            if (unresolved.size() == 0) return;
            if (prevSize == unresolved.size()){
                throw new RuntimeException("Cycle dependencies: " + unresolved.toString());
            }
        }
    }

}
